package com.tdd.tdd;

import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.stream.IntStream;

class OptionParsers {

    public static <T> OptionParser<T> unary(Function<String, T> valParser, T def) {
        return (args, option) -> values(args, option, 1).map(it -> parseValue(it.get(0), valParser, null)).orElse(def);
    }

    public static OptionParser<Boolean> bool() {
        return (args, option) -> values(args, option, 0).isPresent();
    }

    private static Optional<List<String>> values(List<String> args, Option option, int expectedSize) {
        int idx = args.indexOf("-" + option.value());
        if (idx == -1) return Optional.empty();
        List<String> values = values(args, idx);
        if (values.size() < expectedSize) throw new InsufficientArgsException(option.value());
        if (values.size() > expectedSize) throw new TooManyArgsException(option.value());
        return Optional.of(values);
    }


    private static Optional<List<String>> values(List<String> args, Option option) {
        int idx = args.indexOf("-" + option.value());
        return Optional.ofNullable(idx == -1 ? null : values(args, idx));
    }

    private static <T> T parseValue(String value, Function<String, T> valParser, Option option) {
        try {
            return valParser.apply(value);
        } catch (Exception e) {
            throw new IllegalValueException(option.value(), value);
        }
    }

    private static List<String> values(List<String> args, int idx) {
        int followingFlag = IntStream.range(idx + 1, args.size())
                .filter(i -> args.get(i).matches("^-[a-zA-Z-]$")).findFirst().orElse(args.size());
        return args.subList(idx + 1, followingFlag);
    }

    public static <T> OptionParser<T[]> list(IntFunction<T[]> generator, Function<String, T> valueParser) {
        return ((args, option) -> values(args, option)
                .map(it -> it.stream().map(v -> parseValue(v, valueParser, option)).toArray(generator)).orElse(generator.apply(0)));
    }


}
